package com.study.classification

import org.apache.spark.ml.classification.{LogisticRegression, OneVsRest}
import org.apache.spark.ml.evaluation.MulticlassClassificationEvaluator
import org.apache.spark.sql.SparkSession

/**
 * 分类-逻辑回归（多分类）
 *
 * @author stephen
 * @date 2019-08-27 16:20
 */
object MultinomialLogisticRegressionDemo {

  def main(args: Array[String]): Unit = {
    val spark = SparkSession.builder()
      .appName(this.getClass.getSimpleName)
      .master("local[*]")
      .getOrCreate()

    spark.sparkContext.setLogLevel("warn")

    val path = this.getClass.getClassLoader.getResource("data/mllib/sample_multiclass_classification_data.txt").getPath
    val data = spark.read.format("libsvm").load(path)
    // 切分训练集和测试集，使用固定的种子来开始生成随机数。在给定相同的种子下，生成的随机数序列总是相同的。
    val Array(trainingData, testData) = data.randomSplit(Array(0.8, 0.2), seed = 1234L)

    val lr = new LogisticRegression()
      .setMaxIter(10) // 设置最大迭代次数(默认100)，具体迭代过程可能会在不足最大迭代次数时停止
      .setRegParam(0.3) // 设置正则化项系数(默认0.0)，正则化项主要用于防止过拟合现象
      .setElasticNetParam(0.8) // 正则化范式比(默认0.0)，正则化一般有两种范式：L1(Lasso)和L2(Ridge)。L1一般用于特征的稀疏化，L2一般用于防止过拟合。这里的参数即设置L1范式的占比，默认0.0即只使用L2范式。参数值越大表示L1正则所占比例越高，对向量稀疏化效果越好。
      .setTol(1E-7) // 设置容错(默认1E-6)，每次迭代会计算一个误差值，误差值会随着迭代次数的增加逐渐减小，如果误差值小于设置的容错值，则停止迭代优化
      .setLabelCol("label") // 设置标签列(默认读取“label”列)
      .setFeaturesCol("features") // 设置特征列(默认读取“features”列)

    // 训练模型
    //val model = lr.fit(train)
    // LR 和 OneVsRest进行训练
    // OneVsRest将一个给定的二分类算法有效地扩展到多分类问题应用中，也叫做”One-vs-All”算法
    val model = new OneVsRest().setClassifier(lr).fit(trainingData)
    // 系数矩阵、截距向量（OneVsRestModel没有CoefficientMatrix和InterceptVector）
    //println(s"CoefficientMatrix is: \n  ${model.coefficientMatrix}")
    //println(s"InterceptVector is: \n  ${model.interceptVector}")

    // 测试集计算
    val predictions = model.transform(testData)
    predictions.show()
    val evaluator = new MulticlassClassificationEvaluator()
      .setLabelCol("label")
      .setPredictionCol("prediction")

    val accuracy = evaluator.setMetricName("accuracy").evaluate(predictions);
    val weightedPrecision = evaluator.setMetricName("weightedPrecision").evaluate(predictions);
    val weightedRecall = evaluator.setMetricName("weightedRecall").evaluate(predictions);
    val f1 = evaluator.setMetricName("f1").evaluate(predictions);

    println(s"正确率：$accuracy")
    println(s"加权准确率：$weightedPrecision")
    println(s"加权召回率：$weightedRecall")
    println(s"F1值：$f1")

    spark.stop()
  }
}
